/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * Parser for the @{@link ComponentScan} annotation.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.1
 * @see ClassPathBeanDefinitionScanner#scan(String...)
 * @see ComponentScanBeanDefinitionParser
 */
class ComponentScanAnnotationParser {

	private final Environment environment;

	private final ResourceLoader resourceLoader;

	private final BeanNameGenerator beanNameGenerator;

	private final BeanDefinitionRegistry registry;


	public ComponentScanAnnotationParser(Environment environment, ResourceLoader resourceLoader,
			BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry) {

		this.environment = environment;
		this.resourceLoader = resourceLoader;
		this.beanNameGenerator = beanNameGenerator;
		this.registry = registry;
	}


	/**
	 * 解析
	 * @param componentScan    表示@ComponentScan注解的属性值
	 * @param declaringClass   表示@ComponentScan注解所在的类
	 * @return
	 */
	public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
		// todo 创建类路径扫描器(this.registry是spring容器) (componentScan.getBoolean("useDefaultFilters")为true, 会去添加默认过滤器, 这个过滤器就是扫描所有带了@Component注解的类)
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		// todo 获取@ComponentScan中nameGenerator属性, 主要是beanName生成器, 默认是BeanNameGenerator
		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		// todo 是否是内置的名字生成器  (因为@ComponentScan里面的nameGenerator是可以程序员直接指定的, 指定直接的beanName生成器, useInheritedGenerator==true那就是默认的, 为false, 那就是程序员自己指定的)
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		// todo 默认为AnnotationBeanNameGenerator(设置扫描器的名字生成器)
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

		// todo @ComponentScan的scopedProxy属性, 主要是取决于使用哪种代理  (被扫描的bean设置了@scope注解设置了(proxyMode)属性, 用该bean自定义的, 没有用@ComponentScan设置的) (简单理解就是局部变量大于全局变量)
		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		// todo 非默认的代理类型
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			// todo 默认的代理类型需要看@ComponentScan的scopeResolver值, 默认是AnnotationScopeMetadataResolver
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}

		// todo @ComponentScan的resourcePattern属性, 主要加载哪些资源, 默认是加载.class文件 (就是我们要扫描的资源)
		scanner.setResourcePattern(componentScan.getString("resourcePattern"));

		// todo @ComponentScan的includeFilters过滤器
		for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) {
			List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment,
					this.resourceLoader, this.registry);
			for (TypeFilter typeFilter : typeFilters) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
		// todo @ComponentScan的excludeFilters过滤器
		for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) {
			List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment,
				this.resourceLoader, this.registry);
			for (TypeFilter typeFilter : typeFilters) {
				scanner.addExcludeFilter(typeFilter);
			}
		}

		// todo 是否是懒加载  (被扫描的bean设置了@Lazy注解, 用该bean的@@Lazy设置的标识, 没有用@ComponentScan设置的)  (简单理解就是局部变量大于全局变量)
		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}

		// todo 获取所有的包(@ComponentScan中的basePackages和basePackageClasses对应的包)
		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		// todo 解析并放入basePackages属性
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		// todo 解析basePackageClasses属性
		// todo 注意, 这里是解析basePackageClasses对应的包, 即将basePackageClasses对应的包的所有类都加载
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		// todo basePackages为空时, 直接获取当前配置类所在包, 作为扫描路径
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		// todo 添加一个AbstractTypeHierarchyTraversingFilter排除过滤器
		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {

			// todo 扫描出来的类等于我标注了@ComponentScan的类, 我就排除掉 (因为你通过构造方法传递进来的配置类, 早就是一个bean了,  new AnnotationConfigApplicationContext(ZhangXiaoIOC.class) 看 它够着方法里面的register(componentClasses)方法)
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		// todo 扫描
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

}
